package org.piggy.core;

import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry;
import com.alibaba.csp.sentinel.util.TimeUtil;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.piggy.common.config.ServiceDefinition;
import org.piggy.common.config.ServiceInstance;
import org.piggy.common.constants.BasicConst;
import org.piggy.common.utils.NetUtil;
import org.piggy.config.center.api.ConfigCenter;
import org.piggy.core.handler.NacosRegisterCenterHandler;
import org.piggy.core.handler.NacosRulesChangeHandler;
import org.piggy.core.utils.ThreadPoolUtil;
import org.piggy.register.center.api.RegisterCenter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.function.Supplier;

public class Bootstrap {

    private final static Logger log = LoggerFactory.getLogger(Bootstrap.class);

    public static void main(String[] args) {
        Config config = ConfigLoader.getInstance().load(args);
        log.info("port -> {}", config.getPort());

        //spi
        ServiceLoader<ConfigCenter> serviceLoader = ServiceLoader.load(ConfigCenter.class);
        ConfigCenter configCenter = serviceLoader.findFirst().orElseThrow(
                new Supplier<RuntimeException>() {
                    @Override
                    public RuntimeException get() {
                        return new RuntimeException("not found ConfigCenter impl class");
                    }
                });
        configCenter.init(config.getRegistryAddress(), config.getEnv());
        configCenter.subscribeRulesChange(new NacosRulesChangeHandler());

        // init general thread pool
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("piggy-thread-common-pool-%d").build();
        ExecutorService executorService = Executors.newFixedThreadPool(config.getThreadNum(), threadFactory);
        ThreadPoolUtil.getInstance().setExecutorService(executorService);

        Container container = new Container(config);
        container.start();

        RegisterCenter registerCenter = registerAndSubscribe(config);

        registerStateChangeObserver();

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                executorService.shutdown();
                registerCenter.deregister(buildGatewayServiceDefinition(config), buildGatewayServiceInstance(config));
                container.shutdown();
            }
        });
    }

    private static void registerStateChangeObserver() {
        EventObserverRegistry.getInstance().addStateChangeObserver("logging", (prevState, newState, rule, snapshotValue) -> {
            if (newState == CircuitBreaker.State.OPEN) {
                log.info("{} -> OPEN at {}, snapshotValue={}", prevState.name(), TimeUtil.currentTimeMillis(), snapshotValue);
            } else {
                log.info("{} -> {} at {}", prevState.name(), newState.name(), TimeUtil.currentTimeMillis());
            }
        });
    }

    private static RegisterCenter registerAndSubscribe(Config config) {
        ServiceLoader<RegisterCenter> serviceLoader = ServiceLoader.load(RegisterCenter.class);
        RegisterCenter registerCenter = serviceLoader.findFirst().orElseThrow(() -> new RuntimeException("not found RegisterCenter impl"));
        registerCenter.init(config.getRegistryAddress(), config.getEnv());
        //服务定义
        ServiceDefinition serviceDefinition = buildGatewayServiceDefinition(config);
        //服务实例
        ServiceInstance serviceInstance = buildGatewayServiceInstance(config);
        //注册
        registerCenter.register(serviceDefinition, serviceInstance);
        //订阅
        registerCenter.subscribeAllServices(new NacosRegisterCenterHandler());
        return registerCenter;
    }

    private static ServiceInstance buildGatewayServiceInstance(Config config) {
        String localIp = NetUtil.getLocalIp();
        int port = config.getPort();
        ServiceInstance serviceInstance = new ServiceInstance();
        serviceInstance.setServiceInstanceId(localIp + BasicConst.COLON_SEPARATOR + port);
        serviceInstance.setIp(localIp);
        serviceInstance.setPort(port);
        serviceInstance.setRegisterTime(System.currentTimeMillis());
        return serviceInstance;
    }

    private static ServiceDefinition buildGatewayServiceDefinition(Config config) {
        ServiceDefinition serviceDefinition = new ServiceDefinition();
        serviceDefinition.setInvokerMap(Map.of());
        String applicationId = config.getApplicationId();
        String version = config.getVersion();
        serviceDefinition.setServiceId(applicationId);
        serviceDefinition.setVersion(version);
        serviceDefinition.setServiceName(applicationId + BasicConst.COLON_SEPARATOR + version);
        serviceDefinition.setEnvType(config.getEnv());
        return serviceDefinition;
    }

}
